//IO多路复用技术poll函数的使用 
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include "wrap.h"
#include <sys/epoll.h>
#include "pub.h"
#include<sys/stat.h>
#include<fcntl.h>
#include<dirent.h>
#include<signal.h>

int send_header(int cfd,char *CODE,char *msg,char* filetype,int len)
{
	char buf[1024] = {0};
	sprintf(buf,"HTTP/1.1 %s %s\r\n",CODE,msg);
	sprintf(buf+strlen(buf),"Content-Type:%s\r\n",filetype);
	if(len>0)
	{
		sprintf(buf+strlen(buf),"Content-Length:%d\r\n",len);
	}
	strcat(buf,"\r\n");
	Write(cfd,buf,strlen(buf));
	return 0;
}



int send_file(int cfd,char *filename)
{
	int n;
	//打开文件
	int fd = open(filename,O_RDONLY);
	if(fd < 0)
	{
		perror("open error");
		return -1;
	}
	char buf[1024];
	//循环读文件
	while(1)
	{
		memset(buf,0,sizeof(buf));
		n = read(fd,buf,sizeof(buf));
		if(n<=0)
		{
			break;
		}
		else
		{
			Write(cfd,buf,n);
		}
	}
}



int	http_request(int cfd,int epfd)
{
	//若web服务器给浏览器发送数据的时候，浏览器已经关闭连接，此时产生SIGPIPE信号发送给服务端
	struct sigaction act;
	act.sa_handler = SIG_IGN;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGPIPE,&act,NULL);

	int n;
	char buf[1024];
	//读取请求行，分析出要请求的资源文件名
	memset(buf,0,sizeof(buf));
	n = Readline(cfd,buf,sizeof(buf));
	if(n<=0)
	{
		//关闭连接
		close(cfd);
		//下树
		epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,NULL);
		return -1;
	}
	char requestType[16]={0};
	char fileName[255]={0};
	char protocal[16]={0};
	char *pFile = fileName;

	sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]]",requestType,fileName,protocal);
	if(strlen(fileName)<=1)
	{
		strcpy(pFile,"./");
	}
	else
	{
		pFile = fileName+1;	
	}
	//转换汉字编码
	strdecode(pFile,pFile);
	//printf("[%s]\n",requestType);
	printf("[%s]\n",pFile);
	//printf("[%s]\n",protocal);


	//循环读取完剩余的数据
	//read函数读socket文件描述符，读完之后会阻塞。
	//read函数读字符终端、socket文件描述符、管道文件等会阻塞，读磁盘文件一般不会阻塞
	while((n = Readline(cfd,buf,sizeof(buf)))>0);
	//判断文件是否存在
	struct stat st;
	//若文件不存在
	if(stat(pFile,&st)<0)
	{
		printf("file not exit\n");
		//组织应答信息
		send_header(cfd,"404","NOT FOUND",get_mime_type(".html"),0);
		//发送文件内容
		send_file(cfd,"error.html");	
	}
	//若文件存在
	else
	{
		if(S_ISREG(st.st_mode))
		{
			printf("file exit\n");
			send_header(cfd,"200","OK",get_mime_type(pFile),st.st_size);

			send_file(cfd,pFile);
		}
		if(S_ISDIR(st.st_mode))
		{
			char bufdir[1024];
			printf("It is dir\n");
			send_header(cfd,"200","OK",get_mime_type(".html"),0);
			
			send_file(cfd,"./dir_header.html");
			printf("头部文件发送成功\n");
			//文件列表组织
			struct dirent **namelist;
			int num;
			num = scandir(pFile, &namelist, NULL, alphasort);
			if (num < 0)
			{
				perror("scandir");
				close(cfd);
				epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,NULL);
				return -1;
			}
			else {
				while (num--) {
					memset(bufdir,0,sizeof(bufdir));
					//printf("%s\n", namelist[num]->d_name);
					if(namelist[num]->d_type==DT_DIR)
					{
						sprintf(bufdir,"<li><a href=%s/> %s </a></li>",namelist[num]->d_name,namelist[num]->d_name);
					}
					else
					{
						sprintf(bufdir,"<li><a href=%s> %s </a></li>",namelist[num]->d_name,namelist[num]->d_name);
			
					}
					free(namelist[num]);
					Write(cfd,bufdir,strlen(bufdir));
				}
				free(namelist);
			}

			send_file(cfd,"dir_tail.html");
		}

		//目录文件

		//普通文件
	}
	return -1;
}



int main()
{
	//创建socket、设置端口复用，绑定
	int lfd = tcp4bind(9999,NULL);
	//listren
	Listen(lfd,128);
	//创建epoll树
	int epfd = epoll_create(1024);
	if(epfd<0)
	{
		perror("epoll_creat error");
		close(lfd);
		return -1;
	}

	//将监听文件描述符上树
	struct epoll_event ev;
	ev.data.fd = lfd;
	ev.events = EPOLLIN;

	epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);

	int i;
	int cfd;
	int nready;
	int sockfd;
	struct epoll_event events[1024];
	while(1)
	{

		//等待事件发生
		nready = epoll_wait(epfd,events,1024,-1);
		if(nready < 0)	
		{
			if(errno == EINTR)
			{
				continue;
			}
			break;
		}

		for(i = 0;i<nready;i++)
		{
			sockfd = events[i].data.fd;
			//有客户端链接请求
			if(sockfd == lfd)
			{
				//接受新的客户端链接
				cfd = accept(lfd,NULL,NULL);
				//设置cfd为非阻塞
				int flag = fcntl(cfd,F_GETFL);
				flag |= O_NONBLOCK;
				fcntl(cfd, F_SETFL, flag);
				//将新的cfd上树
				ev.data.fd = cfd;
				ev.events = EPOLLIN;
				epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
			}
			else
			{
				//有客户端数据发来
				http_request(sockfd,epfd);
			}

		}
	}


	return 0;
}
